(function (global, factory) {
  // UMD 模块化封装（支持 CommonJS/AMD/全局变量）
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
    typeof define === 'function' && define.amd ? define(factory) :
      (global.MoveLine = factory());
}(this, (function () {
  'use strict';

  /**
   * 路径动画主类
   * @constructor
   * @param {ol.Map} map - OpenLayers 地图实例
   * @param {Object} userOptions - 用户配置
   */
  class MoveLine {
    constructor(map, userOptions) {
      this.map = map;
      this.baseLayer = null;
      this.animationLayer = null;
      this.width = this.map.getSize()[0];
      this.height = this.map.getSize()[1];
      this.markLines = [];
      this.animationFlag = true;
      this.animationEnabled = true;  // 新增动画状态标识
      this._animationFrameId = null; // 新增动画帧ID追踪
      this._initialized = false; // 初始化状态标识

      this.initialize(userOptions);

    }

    // 初始化
    initialize(userOptions) {
      if (this._initialized) return;
      // 默认配置（包含样式参数）
      const defaultOptions = {
        // marker点半径
        markerRadius: 3,
        // marker点颜色,为空或null则默认取线条颜色
        markerColor: '#fff',
        // 线条类型 solid、dashed、dotted
        lineType: 'solid',
        // 线条宽度
        lineWidth: 1,
        // 线条颜色
        colors: ['#F9815C', '#F8AB60', '#EDCC72', '#E2F194', '#94E08A', '#4ECDA5'],
        // 移动点半径
        moveRadius: 2,
        // 移动点颜色
        fillColor: '#fff',
        // 移动点阴影颜色
        shadowColor: '#fff',
        // 移动点阴影大小
        shadowBlur: 5
      };

      // 合并默认配置
      this.options = Object.assign(defaultOptions, userOptions);

      this.initLayers();
      this.setupAnimation();
      this._initialized = true;
    }

    // 初始化画布图层
    initLayers() {
      // 初始化基础绘制层（静态元素：路径线、标记点等）
      this.baseLayer = new CanvasLayer({
        map: this.map,              // 绑定地图实例
        update: () => this.brush()  // 图层更新时触发主绘制方法
      });

      // 初始化动画效果层（动态元素：移动圆点、轨迹拖尾等）
      this.animationLayer = new CanvasLayer({
        map: this.map,                 // 绑定相同地图实例
        update: () => this.render()    // 图层更新时触发动画渲染
      });
    }

    // 设置动画循环
    setupAnimation() {
      const animate = () => {
        if (!this.animationEnabled) return;
        this._animationFrameId = requestAnimationFrame(animate);
        this.render();
      }
      this._animationFrameId = requestAnimationFrame(animate);
    }

    // 核心渲染方法
    brush() {
      // 基础图层绘制方法（静态元素）
      if (!this._initialized) return;  // 确保完成初始化
      console.log('brush');

      // 获取基础图层的2D上下文
      const baseCtx = this.baseLayer.canvas.getContext('2d');
      if (!baseCtx) return;

      // 生成/更新路径数据
      this.addMarkLines();

      // 清空画布准备重绘
      baseCtx.clearRect(0, 0, this.width, this.height);

      // 遍历所有路径进行绘制
      this.markLines.forEach(line => {
        line.drawMarker(baseCtx);     // 绘制起点/终点标记
        line.drawLinePath(baseCtx);   // 绘制连接路径
      })
    }

    render() {
      // 动画图层渲染方法（动态元素）
      if (!this._initialized) return;
      if (!this.animationEnabled) return;   // 检查动画开关状态

      // 获取动画图层上下文
      const animationCtx = this.animationLayer.canvas.getContext('2d');
      if (!animationCtx || !this.animationLayer.visible) return;

      // 清除画布当动画暂停时
      if (!this.animationFlag) {
        animationCtx.clearRect(0, 0, this.width, this.height);
        return;
      }

      // 轨迹拖尾效果实现
      let prev = animationCtx.globalCompositeOperation;
      // 通过合成模式实现轨迹拖尾效果
      animationCtx.globalCompositeOperation = 'destination-in';  // 设置合成模式
      animationCtx.fillStyle = `rgba(0,0,0,0.92)`; // 透明度控制拖尾长度 半透明黑色（0.92透明度决定拖尾长度）
      animationCtx.fillRect(0, 0, this.width, this.height);   // 填充整个画布
      animationCtx.globalCompositeOperation = prev;  // 恢复原始合成模式

      // 遍历所有路径绘制移动圆点
      for (var i = 0; i < this.markLines.length; i++) {
        var markLine = this.markLines[i];
        markLine.drawMoveCircle(animationCtx); // 调用各路径的动画绘制方法 移动圆点
      }
    }

    // 动态更新配置
    update(newOptions) {
      this.options = Object.assign(this.options, newOptions);  // 合并新旧配置（浅合并）
      this.markLines = [];   // 清空所有路径
      this.addMarkLines();   // 重新渲染所有路径
    }

    // 添加路径数据
    addMarkLines() {
      let length = this.options.colors.length;
      this.markLines = this.options.data.map((line, i) => {
        return new MarkLine({
          id: i,
          from: new Marker({
            city: line.from.city,
            location: [line.from.lnglat[0], line.from.lnglat[1]],
            // color: this.options.colors[i % length],
            color: this.options.colors[i],
            map: this.map,
            options: this.options
          }),
          to: new Marker({
            city: line.to.city,
            location: [line.to.lnglat[0], line.to.lnglat[1]],
            // color: this.options.colors[i % length],
            color: this.options.colors[i],
            map: this.map,
            options: this.options
          }),
          map: this.map,
          options: this.options
        })
      })
    }

    show() {
      this.baseLayer?.setVisible(true);
      this.animationLayer?.setVisible(true);
      return this;  // 支持链式调用
    }

    hide() {
      this.baseLayer?.setVisible(false);
      this.animationLayer?.setVisible(false);
      return this;
    }

    toggleVisibility(visible) {
      return visible ? this.show() : this.hide();
    }

    // 完全释放资源
    destroy() {
      if (!this._initialized) return;

      // 销毁图层
      this.baseLayer?.destroy();
      this.animationLayer?.destroy();

      // 清理数据引用
      this.markLines = [];
      this.animationEnabled = true;  // 新增动画状态标识
      this.animationFlag = true;

      if (this._animationFrameId) {
        cancelAnimationFrame(this._animationFrameId);
        this._animationFrameId = null;
      }

      this._initialized = false;
    }

    // 重新初始化 - 需要完全重新加载时使用 - 重新创建实例，保留地图引用，更新配置
    reinitialize(newOptions) {
      this.destroy();
      this.initialize(newOptions);
    }

    // 动画启停控制
    startAnimation() {
      this.animationEnabled = true;
      this.animationFlag = true;
    }

    stopAnimation() {
      this.animationEnabled = false;
      this.animationFlag = false;

      if (this._animationFrameId) {
        cancelAnimationFrame(this._animationFrameId);
        this._animationFrameId = null;
      }
    }

    // 单独重置画布 - 临时清除显示使用 - 局部清理，不影响动画 (问题：缩放或移动地图后又重新渲染了)
    resetCanvas() {
      const resetLayer = (layer) => {
        if (!layer) return;

        const ctx = layer.canvas.getContext('2d');
        ctx.clearRect(0, 0, layer.canvas.width, layer.canvas.height);
      }

      resetLayer(this.baseLayer);
      resetLayer(this.animationLayer);
    }

    // 安全的重置方法 - 数据更新但是配置不变时使用 - 重置数据保持图层 (问题：缩放或移动地图后又重新渲染了)
    softReset() {
      this.startAnimation();
      this.resetCanvas();
      this.markLines = [];
      this.startAnimation();
    }
  }

  /**
   * Canvas 图层管理类
   * @constructor
   * @param {Object} options - 配置参数
   * @param {ol.Map} options.map - OpenLayers 地图实例
   */
  class CanvasLayer {
    constructor(options) {
      this.options = options || {};
      this.paneName = this.options.paneName || 'labelPane';  // 图层容器名称
      this.zIndex = this.options.zIndex || 0;   // 层级控制
      this._map = options.map;   // 绑定的地图实例
      this.visible = true;
      this._eventListeners = []; // 新增事件监听器存储

      this.initialize();
    }

    initialize() {
      // 创建画布并绑定到地图容器
      let canvas = this.canvas = document.createElement('canvas');
      let ctx = this.canvas.getContext('2d');

      // 设置基础定位样式（绝对定位 + z-index 层级控制）
      // canvas.style.cssText = 'position:absolute;' + 'left:0;' + 'top:0;' + 'z-index:' + this.zIndex + ';';
      canvas.style.cssText = `position:absolute; left:0; top:0; z-index: ${this.zIndex};`;

      // 尺寸适配（同步地图尺寸 + Retina 屏适配）
      this.adjustSize();       // 根据地图尺寸调整画布物理尺寸
      this.adjustRatio(ctx);   // 处理高清屏像素比问题

      // 将画布注入地图容器（通过 OpenLayers 的 viewport 元素）
      this._map.getViewport().appendChild(canvas);

      const propertychangeHandler = () => {
        canvas.style.display = 'none';   // 当地图属性变化时（如缩放）暂时隐藏
      }
      const moveendHandler = () => {
        canvas.style.display = 'block';   // 地图操作结束后显示
        this.adjustSize();    // 重新计算尺寸
        this._draw();         // 触发重绘
      }

      // 初始加载延迟执行（确保地图完成初始化布局）
      setTimeout(() => {
        moveendHandler();
      }, 1000);

      // 监听地图事件实现画布动态调整
      this._map.getView().on('propertychange', propertychangeHandler);   // 属性变化监听

      // 地图移动结束后重绘
      this._map.on("moveend", moveendHandler);

      // 存储事件监听器以便销毁（防内存泄漏）
      this._eventListeners = [
        { type: 'propertychange', handler: propertychangeHandler },
        { type: 'moveend', handler: moveendHandler }
      ];

      return this.canvas;  // 返回创建的画布引用
    }

    adjustSize() {
      let width = this._map.getSize()[0];
      let height = this._map.getSize()[1];
      this.canvas.width = width;
      this.canvas.height = height;
      this.canvas.style.width = width + 'px';
      this.canvas.style.height = height + 'px';
    }

    adjustRatio(ctx) {
      // 获取画布上下文支持的像素存储比例（兼容不同浏览器）
      // 该值表示浏览器实际渲染时使用的像素比率，通常普通屏幕为1，Retina屏可能为2
      let backingStore = ctx.backingStorePixelRatio || ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1;
      // 计算实际需要的像素比率 = 设备物理像素比 / 浏览器支持比例
      // window.devicePixelRatio 获取设备物理像素与逻辑像素的比值（如Retina屏为2）
      let pixelRatio = (window.devicePixelRatio || 1) / backingStore;
      // 获取当前画布的逻辑尺寸（CSS像素单位）
      let canvasWidth = ctx.canvas.width;
      let canvasHeight = ctx.canvas.height;
      // 调整画布物理像素尺寸（实际渲染缓冲区大小）
      // 通过放大画布分辨率实现高清渲染（如200%放大）
      ctx.canvas.width = canvasWidth * pixelRatio;
      ctx.canvas.height = canvasHeight * pixelRatio;
      // 保持画布显示尺寸不变（CSS样式尺寸）
      // 避免因物理像素增加导致的元素尺寸异常
      ctx.canvas.style.width = canvasWidth + 'px';
      ctx.canvas.style.height = canvasHeight + 'px';
      // 缩放绘图上下文坐标系
      // 使后续绘图操作自动适应新的像素密度（如用100px绘制时实际使用200物理像素）
      ctx.scale(pixelRatio, pixelRatio);
    }

    _draw() {
      console.log('_draw');

      if (!this.visible) return;  // 可见性检查

      // 获取地图尺寸 [width, height]
      const size = this._map.getSize();
      // 获取当前视图中心点坐标（经纬度）
      const center = this._map.getView().getCenter();
      if (center) {
        // 将地理坐标转换为像素坐标（相对于地图容器）
        const pixel = this._map.getPixelFromCoordinate(center);
        console.log('pixel', pixel);

        // 通过 CSS 定位实现画布居中：
        // left = 中心点X - 地图宽度/2 → 水平居中
        // top = 中心点Y - 地图高度/2 → 垂直居中
        this.canvas.style.left = pixel[0] - size[0] / 2 + 'px';
        this.canvas.style.top = pixel[1] - size[1] / 2 + 'px';

        // 触发图层更新回调（通过 call 绑定 this 到当前 CanvasLayer 实例）
        this.options.update && this.options.update.call(this);
      }
    }

    getContainer() {
      return this.canvas;
    }

    setVisible(visible) {
      this.visible = visible;
      this.canvas.style.display = visible ? 'block' : 'none';
    }

    setZIndex(zIndex) {
      this.canvas.style.zIndex = zIndex;
    }

    getZIndex() {
      return this.zIndex;
    }

    destroy() {
      // 移除DOM元素
      if (this.canvas && this.canvas.parentNode) {
        this.canvas.parentNode.removeChild(this.canvas);
      }

      // 移除事件监听
      this._eventListeners.forEach(({ type, handler }) => {
        if (type === 'propertychange') {
          this._map.getView().un(type, handler)
        } else {
          this._map.un(type, handler);
        }
      })

      // 清理引用
      this._map = null;
      this.canvas = null;
    }
  }

  /**
   * 地图标记点绘制类
   * @constructor
   * @param {Object} opts - 标记点配置参数
   */
  class Marker {
    constructor(opts) {
      this.map = opts.map;
      this.options = opts.options;
      this.city = opts.city;
      this.location = opts.location;
      this.color = opts.color;
    }

    draw(context) {
      // 坐标转换：将地理坐标转换为像素坐标
      let pixel = this.pixel = this.map.getPixelFromCoordinate(this.location);

      // 保存绘图上下文状态（颜色/变换等）
      context.save();

      // 绘制圆形标记点
      context.beginPath();
      context.fillStyle = this.options.markerColor || this.color || 'red';  // 颜色优先级：配置 > 线路颜色
      // context.arc(x, y, radius, startAngle, endAngle, anticlockwise)
      // X 坐标   Y 坐标  半径   起始角度   结束角度   是否逆时针(true 顺时针)
      context.arc(pixel[0], pixel[1], this.options.markerRadius, 0, Math.PI * 2, true);
      context.closePath();
      context.fill();

      // 绘制城市名称标注
      context.textAlign = 'center';
      context.textBaseline = 'middle';
      context.font = '12px Microsoft YaHei';
      context.fillStyle = this.color || '#fff';   // 文字颜色（线路色优先）
      context.fillText(this.city, pixel[0], pixel[1] - 10);

      // 恢复原始绘图状态
      context.restore();
    }
  }

  /**
   * 路径动画线段管理类
   * @constructor
   * @param {Object} opts - 路径配置参数
   * @param {ol.Map} opts.map - OpenLayers 地图实例
   * @param {Object} opts.options - 继承自 MoveLine 的全局样式配置
   * @param {Marker} opts.from - 起点标记实例
   * @param {Marker} opts.to - 终点标记实例
   * @param {number} opts.id - 路径唯一标识符
   */
  class MarkLine {
    constructor(opts) {
      this.map = opts.map;
      this.options = opts.options;
      this.from = opts.from;
      this.to = opts.to;
      this.id = opts.id;
      this.step = 0;   // 动画步进计数器
    }

    // 生成贝塞尔曲线路径点
    getPointList(from, to) {
      // 使用三次贝塞尔曲线生成平滑路径
      var points = [[from[0], from[1]], [to[0], to[1]]];
      var ex = points[1][0];
      var ey = points[1][1];
      points[3] = [ex, ey];
      points[1] = this.getOffsetPoint(points[0], points[3]);
      points[2] = this.getOffsetPoint(points[3], points[0]);
      points = this.smoothSpline(points, false);
      // 修正最后一点在插值产生的偏移
      points[points.length - 1] = [ex, ey];
      return points;
    }

    getOffsetPoint(start, end) {
      var distance = this.getDistance(start, end) / 3; //除以3？
      var angle, dX, dY;
      var mp = [start[0], start[1]];
      var deltaAngle = -0.2; //偏移0.2弧度
      if (start[0] != end[0] && start[1] != end[1]) {
        //斜率存在
        var k = (end[1] - start[1]) / (end[0] - start[0]);
        angle = Math.atan(k);
      } else if (start[0] == end[0]) {
        //垂直线
        angle = (start[1] <= end[1] ? 1 : -1) * Math.PI / 2;
      } else {
        //水平线
        angle = 0;
      }
      if (start[0] <= end[0]) {
        angle -= deltaAngle;
        dX = Math.round(Math.cos(angle) * distance);
        dY = Math.round(Math.sin(angle) * distance);
        mp[0] += dX;
        mp[1] += dY;
      } else {
        angle += deltaAngle;
        dX = Math.round(Math.cos(angle) * distance);
        dY = Math.round(Math.sin(angle) * distance);
        mp[0] -= dX;
        mp[1] -= dY;
      }
      return mp;
    }

    smoothSpline(points, isLoop) {
      var len = points.length;
      var ret = [];
      var distance = 0;
      for (var i = 1; i < len; i++) {
        distance += this.getDistance(points[i - 1], points[i]);
      }
      var segs = distance / 2;
      segs = segs < len ? len : segs;
      for (var i = 0; i < segs; i++) {
        var pos = i / (segs - 1) * (isLoop ? len : len - 1);
        var idx = Math.floor(pos);
        var w = pos - idx;
        var p0;
        var p1 = points[idx % len];
        var p2;
        var p3;
        if (!isLoop) {
          p0 = points[idx === 0 ? idx : idx - 1];
          p2 = points[idx > len - 2 ? len - 1 : idx + 1];
          p3 = points[idx > len - 3 ? len - 1 : idx + 2];
        } else {
          p0 = points[(idx - 1 + len) % len];
          p2 = points[(idx + 1) % len];
          p3 = points[(idx + 2) % len];
        }
        var w2 = w * w;
        var w3 = w * w2;

        ret.push([this.interpolate(p0[0], p1[0], p2[0], p3[0], w, w2, w3), this.interpolate(p0[1], p1[1], p2[1], p3[1], w, w2, w3)]);
      }
      return ret;
    }

    interpolate(p0, p1, p2, p3, t, t2, t3) {
      var v0 = (p2 - p0) * 0.5;
      var v1 = (p3 - p1) * 0.5;
      return (2 * (p1 - p2) + v0 + v1) * t3 + (-3 * (p1 - p2) - 2 * v0 - v1) * t2 + v0 * t + p1;
    }

    getDistance(p1, p2) {
      return Math.sqrt((p1[0] - p2[0]) * (p1[0] - p2[0]) + (p1[1] - p2[1]) * (p1[1] - p2[1]));
    }

    drawMarker(context) {
      this.from.draw(context);
      this.to.draw(context);
    }

    // 路径绘制方法
    drawLinePath(context) {
      // 根据 lineType 绘制实线/虚线
      var pointList = this.path = this.getPointList(this.map.getPixelFromCoordinate(this.from.location), this.map.getPixelFromCoordinate(this.to.location));
      var len = pointList.length;
      context.save();
      context.beginPath();
      context.lineWidth = this.options.lineWidth;
      context.strokeStyle = this.options.colors[this.id] || this.options.colors[0] || "#f98460";
      // context.strokeStyle = this.options.colors[this.id] || this.getRandomColor();

      if (!this.options.lineType || this.options.lineType == 'solid') {
        context.moveTo(pointList[0][0], pointList[0][1]);
        for (var i = 0; i < len; i++) {
          context.lineTo(pointList[i][0], pointList[i][1]);
        }
      } else if (this.options.lineType == 'dashed' || this.options.lineType == 'dotted') {
        for (var i = 1; i < len; i += 2) {
          context.moveTo(pointList[i - 1][0], pointList[i - 1][1]);
          context.lineTo(pointList[i][0], pointList[i][1]);
        }
      }
      context.stroke();
      context.restore();
      this.step = 0; // 缩放地图时重新绘制动画
    }

    // 移动圆点动画方法
    drawMoveCircle(context) {
      // 获取当前路径点列表（兼容首次调用）
      let pointList = this.path || this.getPointList(this.map.getPixelFromCoordinate(this.from.location), this.map.getPixelFromCoordinate(this.to.location));

      // 绘制带平滑过渡的圆点
      context.save();
      context.fillStyle = this.options.fillColor;
      context.shadowColor = this.options.shadowColor;
      context.shadowBlur = this.options.shadowBlur;
      context.beginPath();
      context.arc(pointList[this.step][0], pointList[this.step][1], this.options.moveRadius, 0, Math.PI * 2, true);
      context.fill();
      context.closePath();
      context.restore();

      // 使用 step 控制动画进度
      this.step += 1;
      if (this.step >= pointList.length) {
        this.step = 0;
      }

    }

    getRandomColor() {
      let r = Math.floor(Math.random() * 256);
      let g = Math.floor(Math.random() * 256);
      let b = Math.floor(Math.random() * 256);

      return `rgba(${r},${g},${b},0.8)`
    }
  }

  return MoveLine;
})));